/* Archive Extractor
 * Copyright 2008-2009 Matthew Hoops
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $URL: http://archive-extractor.googlecode.com/svn/trunk/tar.cpp $
 * $Id: tar.cpp 2 2009-03-23 01:29:43Z clone2727 $
 *
 */

#include "arc_dir.h"
#include "tar.h"

UnixTapeArchive::UnixTapeArchive() : StandardArchive() {
}

std::vector<const char*> UnixTapeArchive::getArchiveExtensions() {
	std::vector<const char*> exList;
	exList.push_back("tar");
	return exList;
}

struct UstarHeader {
	char name[100];
	char mode[8];
	char uid[8];
	char gid[8];
	char size[12];
	char mtime[12];
	char checksum[8];
	char typeflag[1];
	char linkname[100];
	char magic[6];
	char version[2];
	char uname[32];
	char gname[32];
	char devmajor[8];
	char devminor[8];
	union ExtraData {
		struct ExtraPosixData{
			char prefix[155];
			char pad[12];
		} extraPosixData;
		
		struct ExtraGnuData {
			char atime[12];
			char ctime[12];
			char offset[12];
			char longnames[4];
			char unused[1];
			struct {
				char offset[12];
				char numbytes[12];
			} sparse[4];
			char isextended[1];
			char realsize[12];
			char pad[17];
		} extraGnuData;
	} extraData;
};

static uint32 octalStringToInt(char* string) {
	static const uint32 baseValue[] = { 1, 8, 64, 512, 4096, 32768, 262144, 2097152, 16777216, 134217728, 1073741824};
	
	char* endChar;
	for (endChar = string; *endChar != ' '; endChar++);
	endChar--;
	
	byte index = 0;
	uint32 value = 0;
	for (; endChar >= string; endChar--) {
		value += (*endChar - '0') * baseValue[index];
		index++;
	}
	
	return value;
}

int UnixTapeArchive::detect(FILE* file) {
	// Only the ustar format is supported for now
	fseek(file, 0x101, SEEK_SET);
	static const char magic[5] = { 'u', 's', 't', 'a', 'r' };
	for (byte i = 0; i < 5; i++)
		if (readByte(file) != magic[i])
			return 0;
	return 100;
}

void UnixTapeArchive::checkDirectory(const char* filename) {
	
}

bool UnixTapeArchive::extractFiles(FILE* file) {
	while (!feof(file)) {
		UstarHeader tarHeader;
		fread(&tarHeader, 1, 345, file);
		
		if (tarHeader.version[0] == '0' && tarHeader.version[1] == '0') {
			fread(&tarHeader.extraData.extraPosixData, 1, sizeof(UstarHeader::ExtraData::ExtraPosixData), file);
		} else if (tarHeader.version[0] == ' ' && tarHeader.version[1] == 0) {
			fread(&tarHeader.extraData.extraGnuData, 1, sizeof(UstarHeader::ExtraData::ExtraGnuData), file);
		} else {
			return false;
		}
		
		switch (tarHeader.typeflag[0]) {
			case 0:
			case '0': { // Regular File
				printf ("Extracting file \'%s\'...\n", tarHeader.name);
				checkDirectory(tarHeader.name);
				FILE *output = fopen(tarHeader.name, "wb");
				if (!output) {
					printf ("Could not open output file!\n");
					return false;
				}
				uint32 filesize = octalStringToInt(tarHeader.size);
				byte *buffer = (byte *)malloc(filesize);
				if (!buffer) {
					printf ("Could not allocate memory!\n");
					return false;
				}
				fread(buffer, 1, filesize, file);
				fwrite(buffer, 1, filesize, output);
				fflush(output);
				free(buffer);
				fclose(output);
				fseek(file, (filesize % 512 == 0) ? 0 : 512 - (filesize % 512), SEEK_CUR);
				} break;
			case '5': // Directory
				mkdir(tarHeader.name);
				break;
			case '1': // Hard Link
			case '2': // Symbolic Link
			case '3': // Character Device Node
			case '4': // Block Device Node
			case '6': // FIFO Directory
			case '7': // Reserved
				// Ignore!
				fseek(file, octalStringToInt(tarHeader.size), SEEK_CUR);
				break;
			default:
				return false;
		}
	}
	return true;
}
